package simpledb.log;

import simpledb.server.SimpleDB;
import simpledb.file.*;
import static simpledb.file.Page.*;
import java.util.*;

/**
 * The low-level log manager. This log manager is responsible for writing log
 * records into a log file. A log record can be any sequence of integer and
 * string values. The log manager does not understand the meaning of these
 * values, which are written and read by the
 * {@link simpledb.tx.recovery.RecoveryMgr recovery manager}.<br>
 * 低级日志管理器。 此日志管理器负责将日志记录写入日志文件。 日志记录可以是整数和字符串值的任何序列。 日志管理器不理解这些的含义 值，由
 * {@link simpledb.tx.recovery.RecoveryMgr recovery manager}写入和读取 。<br>
 * (LSN log sequence number)
 * 
 * @author Edward Sciore
 */
public class LogMgr implements Iterable<BasicLogRecord> {
  /**
   * The location where the pointer to the last integer in the page is. A value
   * of 0 means that the pointer is the first value in the page.</br>
   * 指向页面中最后一个整数位置的指针。 值为0表示指针是页面中的第一个值。
   */
  public static final int LAST_POS = 0;

  private String logfile;
  private Page mypage = new Page();
  private Block currentblk;
  private int currentpos;

  /**
   * Creates the manager for the specified log file. If the log file does not
   * yet exist, it is created with an empty first block. This constructor
   * depends on a {@link FileMgr} object that it gets from the method
   * {@link simpledb.server.SimpleDB#fileMgr()}. That object is created during
   * system initialization. Thus this constructor cannot be called until
   * {@link simpledb.server.SimpleDB#initFileMgr(String)} is called first. <br>
   * 为指定的日志文件创建管理器。 如果日志文件不存在， 它是用空的第一个块创建的。 这个构造函数 取决于从方法中
   * {@link simpledb.server.SimpleDB＃fileMgr（)}获得的{@link FileMgr}对象 。 该对象在创建期间
   * 系统初始化。 因此，这个构造函数在 {@link simpledb.server.SimpleDB#initFileMgr(String)}
   * 之后被调用。
   * 
   * @param logfile the name of the log file
   */
  public LogMgr(String logfile) {
    this.logfile = logfile;
    int logsize = SimpleDB.fileMgr().size(logfile);
    if (logsize == 0)
      appendNewBlock();
    else {
      currentblk = new Block(logfile, logsize - 1);
      mypage.read(currentblk);
      currentpos = getLastRecordPosition() + INT_SIZE;
    }
  }

  /**
   * Ensures that the log records corresponding to the specified LSN(log
   * sequence number) has been written to disk. All earlier log records will
   * also be written to disk. <br>
   * 确保与指定LSN(日志序列号)对应的日志记录已写入磁盘。 所有较早的日志记录也将写入磁盘。
   * 
   * @param lsn the LSN of a log record
   */
  public void flush(int lsn) {
    if (lsn >= currentLSN())
      flush();
  }

  /**
   * Returns an iterator for the log records, which will be returned in reverse
   * order starting with the most recent.<br>
   * 返回日志记录的迭代器，它将按照从最近开始的相反顺序返回。
   * 
   * @see java.lang.Iterable#iterator()
   */
  public synchronized Iterator<BasicLogRecord> iterator() {
    flush();
    return new LogIterator(currentblk);
  }

  /**
   * Appends a log record to the file. The record contains an arbitrary array of
   * strings and integers. The method also writes an integer to the end of each
   * log record whose value is the offset of the corresponding integer for the
   * previous log record. These integers allow log records to be read in reverse
   * order.<br>
   * 将日志记录附加到文件。 记录包含字符串和整数的任意数组。 该方法还将整数写入每个日志记录的末尾，该日志记录的值是上一个日志记录的相应整数的偏移量。
   * 这些整数允许以相反的顺序读取日志记录。
   * 
   * @param rec the list of values
   * @return the LSN of the final value
   */
  public synchronized int append(Object[] rec) {
    // 指向上一个日志记录的整数的4个字节
    int recsize = INT_SIZE; // 4 bytes for the integer that points to the
                            // previous log record
    for (Object obj : rec)
      recsize += size(obj);
    if (currentpos + recsize >= BLOCK_SIZE) { // the log record doesn't fit,
      flush(); // so move to the next block.
      appendNewBlock();
    }
    for (Object obj : rec)
      appendVal(obj);
    finalizeRecord();
    return currentLSN();
  }

  /**
   * Adds the specified value to the page at the position denoted by currentpos.
   * Then increments currentpos by the size of the value.<br>
   * 将指定的值添加到由currentpos表示的位置的页面。 然后将currentpos增加该值的大小。
   * 
   * @param val the integer or string to be added to the page
   */
  private void appendVal(Object val) {
    if (val instanceof String)
      mypage.setString(currentpos, (String) val);
    else
      mypage.setInt(currentpos, (Integer) val);
    currentpos += size(val);
  }

  /**
   * Calculates the size of the specified integer or string.<br>
   * 计算指定的整数或字符串的大小。
   * 
   * @param val the value
   * @return the size of the value, in bytes
   */
  private int size(Object val) {
    if (val instanceof String) {
      String sval = (String) val;
      return STR_SIZE(sval.length());
    } else
      return INT_SIZE;
  }

  /**
   * Returns the LSN of the most recent log record. As implemented, the LSN is
   * the block number where the record is stored. Thus every log record in a
   * block has the same LSN.<br>
   * 返回最近日志记录的LSN。 实现时，LSN是存储记录的块号。 因此，块中的每个日志记录都具有相同的LSN。
   * 
   * @return the LSN of the most recent log record
   */
  private int currentLSN() {
    return currentblk.number();
  }

  /**
   * Writes the current page to the log file. <br>
   * 将当前page写入到日志文件
   */
  private void flush() {
    mypage.write(currentblk);
  }

  /**
   * Clear the current page, and append it to the log file.<br>
   * 清除当前页面，并将其附加到日志文件。
   */
  private void appendNewBlock() {
    setLastRecordPosition(0);
    currentpos = INT_SIZE;
    currentblk = mypage.append(logfile);
  }

  /**
   * Sets up a circular chain of pointers to the records in the page. There is
   * an integer added to the end of each log record whose value is the offset of
   * the previous log record. The first four bytes of the page contain an
   * integer whose value is the offset of the integer for the last log record in
   * the page.<br>
   * 设置指向页面中记录的指针的环形链表。 有一个整数添加到每个日志记录的结尾，其值是上一个日志记录的偏移量。
   * 页面的前四个字节包含一个整数，其值是页面中最后一条日志记录的整数偏移量。
   */
  private void finalizeRecord() {
    mypage.setInt(currentpos, getLastRecordPosition());
    setLastRecordPosition(currentpos);
    currentpos += INT_SIZE;
  }

  private int getLastRecordPosition() {
    return mypage.getInt(LAST_POS);
  }

  private void setLastRecordPosition(int pos) {
    mypage.setInt(LAST_POS, pos);
  }
}
